package com.nulldev.util.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ByteUtil;

public class DynamicByteArrayInputStream extends InputStream {
	/**
	 * An array of bytes that was provided by the creator of the stream. Elements
	 * <code>buf[0]</code> through <code>buf[count-1]</code> are the only bytes that
	 * can ever be read from the stream; element <code>buf[pos]</code> is the next
	 * byte to be read.
	 */
	protected volatile byte buf[];

	/**
	 * The index of the next character to read from the input stream buffer. This
	 * value should always be nonnegative and not larger than the value of
	 * <code>count</code>. The next byte to be read from the input stream buffer
	 * will be <code>buf[pos]</code>.
	 */
	protected volatile int pos;

	/**
	 * The currently marked position in the stream. ByteArrayInputStream objects are
	 * marked at position zero by default when constructed. They may be marked at
	 * another position within the buffer by the <code>mark()</code> method. The
	 * current buffer position is set to this point by the <code>reset()</code>
	 * method.
	 * <p>
	 * If no mark has been set, then the value of mark is the offset passed to the
	 * constructor (or 0 if the offset was not supplied).
	 *
	 * @since JDK1.1
	 */
	protected volatile int mark = 0;

	/**
	 * The index one greater than the last valid character in the input stream
	 * buffer. This value should always be nonnegative and not larger than the
	 * length of <code>buf</code>. It is one greater than the position of the last
	 * byte within <code>buf</code> that can ever be read from the input stream
	 * buffer.
	 */
	protected volatile int count;

	protected volatile int length;

	private volatile boolean allowFinish;

	/**
	 * Creates a <code>ByteArrayInputStream</code> so that it uses <code>buf</code>
	 * as its buffer array. The buffer array is not copied. The initial value of
	 * <code>pos</code> is <code>0</code> and the initial value of
	 * <code>count</code> is the length of <code>buf</code>.
	 *
	 * @param buf the input buffer.
	 */
	public DynamicByteArrayInputStream(int length) {
		this.buf = new byte[length];
		this.pos = 0;
		this.count = length;
		this.length = 0;
	}

	/**
	 * Creates a <code>ByteArrayInputStream</code> so that it uses <code>buf</code>
	 * as its buffer array. The buffer array is not copied. The initial value of
	 * <code>pos</code> is <code>0</code> and the initial value of
	 * <code>count</code> is the length of <code>buf</code>.
	 *
	 * @param buf the input buffer.
	 */
	public DynamicByteArrayInputStream(byte buf[]) {
		this.buf = buf;
		this.pos = 0;
		this.count = buf.length;
		this.length = buf.length;
	}

	/**
	 * Creates <code>ByteArrayInputStream</code> that uses <code>buf</code> as its
	 * buffer array. The initial value of <code>pos</code> is <code>offset</code>
	 * and the initial value of <code>count</code> is the minimum of
	 * <code>offset+length</code> and <code>buf.length</code>. The buffer array is
	 * not copied. The buffer's mark is set to the specified offset.
	 *
	 * @param buf    the input buffer.
	 * @param offset the offset in the buffer of the first byte to read.
	 * @param length the maximum number of bytes to read from the buffer.
	 */
	public DynamicByteArrayInputStream(byte buf[], int offset, int length) {
		this.buf = buf;
		this.pos = offset;
		this.count = Math.min(offset + length, buf.length);
		this.mark = offset;
		this.length = length;
	}

	/**
	 * Reads the next byte of data from this input stream. The value byte is
	 * returned as an <code>int</code> in the range <code>0</code> to
	 * <code>255</code>. If no byte is available because the end of the stream has
	 * been reached, the value <code>-1</code> is returned.
	 * <p>
	 * This <code>read</code> method cannot block.
	 *
	 * @return the next byte of data, or <code>-1</code> if the end of the stream
	 *         has been reached.
	 */
	@Override
	public synchronized int read() {
		final int preR = (pos < count) ? (buf[pos++] & 0xff) : -1;
		if (preR != -1) {
			return preR;
		} else if (allowFinish) {
			return -1;
		} else {
			return 0;
		}
	}

	@Override
	public synchronized int read(final byte[] b) {
		return this.read(b, 0, b.length);
	}

	/**
	 * Reads up to <code>len</code> bytes of data into an array of bytes from this
	 * input stream. If <code>pos</code> equals <code>count</code>, then
	 * <code>-1</code> is returned to indicate end of file. Otherwise, the number
	 * <code>k</code> of bytes read is equal to the smaller of <code>len</code> and
	 * <code>count-pos</code>. If <code>k</code> is positive, then bytes
	 * <code>buf[pos]</code> through <code>buf[pos+k-1]</code> are copied into
	 * <code>b[off]</code> through <code>b[off+k-1]</code> in the manner performed
	 * by <code>System.arraycopy</code>. The value <code>k</code> is added into
	 * <code>pos</code> and <code>k</code> is returned.
	 * <p>
	 * This <code>read</code> method cannot block.
	 *
	 * @param b   the buffer into which the data is read.
	 * @param off the start offset in the destination array <code>b</code>
	 * @param len the maximum number of bytes read.
	 * @return the total number of bytes read into the buffer, or <code>-1</code> if
	 *         there is no more data because the end of the stream has been reached.
	 * @exception NullPointerException      If <code>b</code> is <code>null</code>.
	 * @exception IndexOutOfBoundsException If <code>off</code> is negative,
	 *                                      <code>len</code> is negative, or
	 *                                      <code>len</code> is greater than
	 *                                      <code>b.length - off</code>
	 */
	@Override
	public synchronized int read(byte b[], int off, int len) {
		if (b == null) {
			throw new NullPointerException();
		} else if (off < 0 || len < 0 || len > b.length - off) {
			throw new IndexOutOfBoundsException();
		}
		JVM.println("read: b.length: " + b.length + ", off: " + off + ", len: " + len);

		if (pos >= count) {
			if (allowFinish)
				return -1;
			return 0;
		}

		int avail = count - pos;
		if (len > avail) {
			len = avail;
		}
		if (len <= 0 && allowFinish) {
			return -1;
		} else if (len <= 0) {
			return 0;
		}
		final byte[] o = remove(buf, pos, len);
		System.arraycopy(o, 0, b, off, len);
		if (len != -1) {
			return len;
		} else {
			if (allowFinish)
				return -1;
			return 0;
		}
	}

	private byte[] remove(byte[] buffer, int offset, int length) {
		System.arraycopy(this.buf, pos, buffer, offset, length);
		this.buf = Arrays.copyOf(this.buf, length);
		this.pos += length;
		return buffer;
	}

	/**
	 * Skips <code>n</code> bytes of input from this input stream. Fewer bytes might
	 * be skipped if the end of the input stream is reached. The actual number
	 * <code>k</code> of bytes to be skipped is equal to the smaller of
	 * <code>n</code> and <code>count-pos</code>. The value <code>k</code> is added
	 * into <code>pos</code> and <code>k</code> is returned.
	 *
	 * @param n the number of bytes to be skipped.
	 * @return the actual number of bytes skipped.
	 */
	public synchronized long skip(long n) {
		long k = count - pos;
		if (n < k) {
			k = n < 0 ? 0 : n;
		}

		pos += k;
		return k;
	}

	/**
	 * Returns the number of remaining bytes that can be read (or skipped over) from
	 * this input stream.
	 * <p>
	 * The value returned is <code>count&nbsp;- pos</code>, which is the number of
	 * bytes remaining to be read from the input buffer.
	 *
	 * @return the number of remaining bytes that can be read (or skipped over) from
	 *         this input stream without blocking.
	 */
	public synchronized int available() {
		final int o = (count - pos <= -1 ? 0 : count - pos); // was 8192
		return o;
	}

	/**
	 * Tests if this <code>InputStream</code> supports mark/reset. The
	 * <code>markSupported</code> method of <code>ByteArrayInputStream</code> always
	 * returns <code>true</code>.
	 *
	 * @since JDK1.1
	 */
	public boolean markSupported() {
		return true;
	}

	/**
	 * Set the current marked position in the stream. ByteArrayInputStream objects
	 * are marked at position zero by default when constructed. They may be marked
	 * at another position within the buffer by this method.
	 * <p>
	 * If no mark has been set, then the value of the mark is the offset passed to
	 * the constructor (or 0 if the offset was not supplied).
	 *
	 * <p>
	 * Note: The <code>readAheadLimit</code> for this class has no meaning.
	 *
	 * @since JDK1.1
	 */
	public void mark(int readAheadLimit) {
		mark = pos;
	}

	/**
	 * Resets the buffer to the marked position. The marked position is 0 unless
	 * another position was marked or an offset was specified in the constructor.
	 */
	public synchronized void reset() {
		pos = mark;
	}

	/**
	 * Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in this
	 * class can be called after the stream has been closed without generating an
	 * <tt>IOException</tt>.
	 */
	public void close() throws IOException {
	}

	public void addData(final byte[] data) {
		this.buf = ByteUtil.concatenateByteArrays(this.buf, data);
		this.length += data.length;
	}

	public void addData(final int data) {
		this.buf = ByteUtil.concatenateByteArrays(this.buf, new byte[]
			{ (byte) data });
		this.length += 1;
	}

	public void write(final int data) {
		this.addData(data);
	}

	public void write(final byte[] data) {
		this.addData(data);
	}

	public boolean hasData() {
		return this.buf.length > 0;
	}

	public int bufferSize() {
		return this.buf.length;
	}

	public void finish() {
		this.allowFinish = true;
	}

	public byte[] buffer() {
		return this.buf;
	}

	public void changeCacheSize(final int cacheSize) {
		if (cacheSize < 1)
			throw new IllegalArgumentException("cacheSize < 1!");
		final byte[] old_cache = this.buf;
		this.buf = ByteUtil.concatenateByteArrays(old_cache, new byte[cacheSize - old_cache.length]);
	}

	public void set(final byte[] initData) {
		if (initData == null)
			return;
		this.buf = initData;
	}

	public boolean isEmpty() {
		JVM.println("length: " + this.length + ", buf: " + new String(buf));
		return this.length == 0;
	}
}
